msg_tool\scripts\yaneurao\itufuru/
script.rs

1//! Yaneurao Itufuru Script File
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::{decode_to_string, encode_string};
6use anyhow::Result;
7
8#[derive(Debug)]
9/// Yaneurao Itufuru Script Builder
10pub struct ItufuruScriptBuilder {}
11
12impl ItufuruScriptBuilder {
13    /// Creates a new instance of `ItufuruScriptBuilder`
14    pub const fn new() -> Self {
15        ItufuruScriptBuilder {}
16    }
17}
18
19impl ScriptBuilder for ItufuruScriptBuilder {
20    fn default_encoding(&self) -> Encoding {
21        Encoding::Cp932
22    }
23
24    fn build_script(
25        &self,
26        data: Vec<u8>,
27        _filename: &str,
28        encoding: Encoding,
29        _archive_encoding: Encoding,
30        config: &ExtraConfig,
31        _archive: Option<&Box<dyn Script>>,
32    ) -> Result<Box<dyn Script>> {
33        Ok(Box::new(ItufuruScript::new(data, encoding, config)?))
34    }
35
36    fn extensions(&self) -> &'static [&'static str] {
37        &[]
38    }
39
40    fn script_type(&self) -> &'static ScriptType {
41        &ScriptType::YaneuraoItufuru
42    }
43}
44
45#[derive(Debug)]
46struct ItufuruString {
47    instr: u16,
48    len_pos: usize,
49    len: u16,
50}
51
52#[derive(Debug)]
53/// Yaneurao Itufuru Script
54pub struct ItufuruScript {
55    data: MemReader,
56    strings: Vec<ItufuruString>,
57    encoding: Encoding,
58}
59
60impl ItufuruScript {
61    /// Creates a new `ItufuruScript`
62    ///
63    /// * `buf` - The buffer containing the script data
64    /// * `encoding` - The encoding used for the script
65    /// * `config` - Extra configuration options
66    pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
67        let mut reader = MemReader::new(buf);
68        let mut strings = Vec::new();
69        let len = reader.data.len();
70
71        while reader.pos + 1 < len {
72            let instr = reader.read_u16()?;
73            // 普通文本 0x2
74            // 选项     0x1e
75            // 文件名   0x1
76            // 背景     0x13
77            // 声音     0x27
78            if instr == 0x2 || instr == 0x1e || instr == 0x1 || instr == 0x13 || instr == 0x27 {
79                let len_pos = reader.pos;
80                let len = reader.read_u16()?;
81                match reader.read_cstring() {
82                    Ok(s) => {
83                        let slen = s.as_bytes_with_nul().len() as u16;
84                        if slen != len {
85                            reader.pos = len_pos;
86                            continue;
87                        }
88                        if instr == 0x2 && !s.as_bytes().ends_with(b"\n") {
89                            reader.pos = len_pos;
90                            continue;
91                        }
92                        if len < 3 {
93                            reader.pos = len_pos;
94                            continue;
95                        }
96                        if instr != 0x2 && instr != 0x1e {
97                            continue;
98                        }
99                        strings.push(ItufuruString {
100                            instr,
101                            len_pos,
102                            len,
103                        });
104                    }
105                    Err(_) => {
106                        reader.pos = len_pos;
107                        continue;
108                    }
109                }
110            }
111        }
112
113        Ok(ItufuruScript {
114            data: reader,
115            strings,
116            encoding,
117        })
118    }
119}
120
121impl Script for ItufuruScript {
122    fn default_output_script_type(&self) -> OutputScriptType {
123        OutputScriptType::Json
124    }
125
126    fn default_format_type(&self) -> FormatOptions {
127        FormatOptions::None
128    }
129
130    fn extract_messages(&self) -> Result<Vec<Message>> {
131        let mut messages = Vec::new();
132        for i in self.strings.iter() {
133            let str_pos = i.len_pos + 2; // Skip the length bytes
134            let s = self.data.cpeek_cstring_at(str_pos as u64)?;
135            let decoded = decode_to_string(self.encoding, s.as_bytes(), true)?;
136            messages.push(Message {
137                name: None,
138                message: decoded,
139            });
140        }
141        Ok(messages)
142    }
143
144    fn import_messages<'a>(
145        &'a self,
146        messages: Vec<Message>,
147        mut file: Box<dyn WriteSeek + 'a>,
148        _filename: &str,
149        encoding: Encoding,
150        replacement: Option<&'a ReplacementTable>,
151    ) -> Result<()> {
152        if self.strings.len() != messages.len() {
153            return Err(anyhow::anyhow!(
154                "Number of messages does not match the number of strings in the script"
155            ));
156        }
157        let mut old_pos = 0;
158        for (old, new) in self.strings.iter().zip(messages) {
159            if old_pos < old.len_pos {
160                file.write_all(&self.data.data[old_pos..old.len_pos])?;
161                old_pos = old.len_pos;
162            }
163            let mut nstr = new.message;
164            if let Some(repl) = replacement {
165                for (from, to) in repl.map.iter() {
166                    nstr = nstr.replace(from, to);
167                }
168            }
169            if old.instr == 0x2 && !nstr.ends_with('\n') {
170                nstr.push('\n');
171            }
172            let encoded = encode_string(encoding, &nstr, false)?;
173            let new_len = encoded.len() as u16 + 1;
174            file.write_u16(new_len)?;
175            file.write_all(&encoded)?;
176            file.write_all(&[0])?; // Null terminator
177            old_pos += 2 + old.len as usize;
178        }
179        if old_pos < self.data.data.len() {
180            file.write_all(&self.data.data[old_pos..])?;
181        }
182        Ok(())
183    }
184}